/*
 * @(#)MarqueePane.java 6/9/2009
 *
 * Copyright 2002 - 2009 JIDE Software Inc. All rights reserved.
 *
 */

package com.element.ui.pane;

import javax.swing.*;
import java.awt.*;

/**
 * MarqueePane是JScrollPane的子类，具有自动滚动功能。在MarqueePane中，您可以定义希望MarqueePane内的组件滚动到的方向和滚动速度。
 * <p>
 * 支持的特性
 * <ul>
 *     <li>更改滚动方向。它可以向上、向下、向右或向左滚动。</li>
 *     <li>更改滚动数量。它在每个循环中滚动的像素数。它越小，看起来就越平滑</li>
 *     <li>卷轴和停留。例如，当您滚动一个文本时，它可以逐行滚动，并在每一行上停留一段时间，以便可以读取这一行上的全文。</li>
 * </ul>
 */
public class MarqueePane extends JScrollPane {
	private int _scrollDelay = 100;
	private int _stayDelay = 2000;
	private int _scrollAmount = 2;
	private int _scrollDirection = SCROLL_DIRECTION_LEFT;
	private int _stayPosition = -1;

	private Timer _scrollTimer = null;
	private int _scrollTimes;
	private boolean _reachStayPosition = false;

	public static final int SCROLL_DIRECTION_LEFT = 0;
	public static final int SCROLL_DIRECTION_RIGHT = 1;
	public static final int SCROLL_DIRECTION_UP = 2;
	public static final int SCROLL_DIRECTION_DOWN = 3;

	public MarqueePane(Component view) {
		super(view);
		startAutoScrolling();
	}

	public MarqueePane() {
		super();
		startAutoScrolling();
	}

	@Override
	public void updateUI() {
		super.updateUI();
		setViewportBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
		setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
		setWheelScrollingEnabled(false);
	}

	/**
	 * Get the scroll frequency which indicating how frequent the Marquee will get repaint.
	 * <p/>
	 * The default value is 100ms.
	 *
	 * @return the scroll frequency.
	 */
	public int getScrollDelay() {
		return _scrollDelay;
	}

	/**
	 * Set the scroll frequency which indicating how frequent the Marquee will get repaint.
	 *
	 * @param scrollDelay the scroll frequency
	 */
	public void setScrollDelay(int scrollDelay) {
		_scrollDelay = scrollDelay;
	}

	/**
	 * Get the scroll amount between two repaints.
	 * <p/>
	 * The default value is 2.
	 *
	 * @return the step size.
	 */
	public int getScrollAmount() {
		return _scrollAmount;
	}

	/**
	 * Set the scroll amount between two repaints.
	 *
	 * @param scrollAmount the step size
	 */
	public void setScrollAmount(int scrollAmount) {
		_scrollAmount = scrollAmount;
	}

	/**
	 * Get the scroll direction.
	 * <p/>
	 * The value could be <code>SCROLL_LEFT</code>, <code>SCROLL_RIGHT</code>, <code>SCROLL_UP</code>,
	 * <code>SCROLL_DOWN</code>
	 * <p/>
	 * The default value is <code>SCROLL_LEFT</code>.
	 *
	 * @return the scroll direction.
	 */
	public int getScrollDirection() {
		return _scrollDirection;
	}

	/**
	 * set the scroll direction.
	 *
	 * @param scrollDirection the scroll direction
	 */
	public void setScrollDirection(int scrollDirection) {
		_scrollDirection = scrollDirection;
	}

	/**
	 * Gets delay time when it reaches a stay position.
	 * <p/>
	 * The default value is 500ms.
	 *
	 * @return the delay time when it reaches a stay position..
	 */
	public int getStayDelay() {
		return _stayDelay;
	}

	/**
	 * Sets stay delay time when it reaches a stay position.
	 *
	 * @param stayDelay the delay time when it reaches a stay position.
	 */
	public void setStayDelay(int stayDelay) {
		_stayDelay = stayDelay;
	}

	/**
	 * Stop auto scrolling. The view will stay where it is.
	 */
	public void stopAutoScrolling() {
		if (_scrollTimer != null) {
			if (_scrollTimer.isRunning()) {
				_scrollTimer.stop();
			}
			_scrollTimer = null;
		}
	}

	/**
	 * Start auto scrolling.
	 */
	public void startAutoScrolling() {
		stopAutoScrolling();
		_scrollTimer = new Timer(getScrollDelay(), e -> {
			BoundedRangeModel rangeModel;
			if (getScrollDirection() == SCROLL_DIRECTION_LEFT || getScrollDirection() == SCROLL_DIRECTION_RIGHT) {
				rangeModel = getHorizontalScrollBar().getModel();
			} else {
				rangeModel = getVerticalScrollBar().getModel();
			}
			int value = rangeModel.getValue();

			if (getScrollDirection() == SCROLL_DIRECTION_LEFT || getScrollDirection() == SCROLL_DIRECTION_UP) {
				if (value + rangeModel.getExtent() == rangeModel.getMaximum()) {
					rangeModel.setValue(rangeModel.getMinimum());
					_scrollTimes = 0;
				} else if (value + getScrollAmount() + rangeModel.getExtent() > rangeModel.getMaximum()) {
					rangeModel.setValue(rangeModel.getMaximum() - rangeModel.getExtent());
				} else {
					rangeModel.setValue(value + getScrollAmount());
					_scrollTimes++;
				}
				_reachStayPosition = rangeModel.getValue() == rangeModel.getMinimum();
			} else {
				if (value == rangeModel.getMinimum()) {
					int maximum = rangeModel.getMaximum();
					int extent = rangeModel.getExtent();
					rangeModel.setValue(rangeModel.getMaximum() - rangeModel.getExtent());
					// In the very beginning, range model need to be reset so we need adjust the value with it.
					if (maximum != rangeModel.getMaximum() || extent != rangeModel.getExtent()) {
						rangeModel.setValue(rangeModel.getMaximum() - rangeModel.getExtent());
					}
					_scrollTimes = 0;
				} else if (value - getScrollAmount() < rangeModel.getMinimum()) {
					rangeModel.setValue(rangeModel.getMinimum());
				} else {
					rangeModel.setValue(value - getScrollAmount());
					_scrollTimes++;
				}
				_reachStayPosition = rangeModel.getValue() == rangeModel.getMinimum() || rangeModel.getValue() == rangeModel.getMaximum();
			}
			if (!_reachStayPosition && getStayPosition() >= 0) {
				if (_scrollTimes % getStayPosition() == 0) {
					_reachStayPosition = true;
				}
			}
			if (_scrollTimer != null) {
				_scrollTimer.setDelay(_reachStayPosition ? getStayDelay() : getScrollDelay());
			}
		});
		_scrollTimer.start();
	}

	/**
	 * Get stay position.
	 * <p/>
	 * With this field, you can let the scrolling stop for <code>getStayTime</code> after scrolling <code>getStayPosition</code> times.
	 * <p/>
	 * The default value is -1, which means no stay position in the middle.
	 *
	 * @return the stay position.
	 */
	public int getStayPosition() {
		return _stayPosition;
	}

	/**
	 * Set stay position.
	 *
	 * @param stayPosition the stay position
	 */
	public void setStayPosition(int stayPosition) {
		_stayPosition = stayPosition;
	}
}
